Skip to content

Enable geom_ribbon() to draw separate lines for upper and lower intervals #3529

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged

Conversation

yutannihilation
Copy link
Member

Fix #3503

This PR changes GeomRibbon from a normal polygon to a polygon + two lines; while GeomRibbon is implemented as a polygon, considering that the nature of this geom is to indicate the upper and the lower range along X axis, I believe it's natural that GeomRibbon comes with two lines.

Though this is a breaking change, I expect there's very few users using GeomRibbon as a closed line. But, I'm not fully confident... If this expectation would fail, I'll withdraw this PR and create a new geom.

devtools::load_all("~/Documents/repo/ggplot2")
#> Loading ggplot2

df <- tibble::tibble(
  y = c(0, 0, 0, 0, 0, 1, 1.5, 1.7, 1.8), 
  upper_y = y + 0.2, 
  lower_y = y - 0.2,
  year = -4:4
)

# can divide the ribbon
df[4, "upper_y"] <- NA 
df[4, "lower_y"] <- NA

ggplot(df, aes(year, y)) + geom_line() + 
  geom_ribbon(aes(ymax = upper_y, ymin = lower_y),
              linetype = 2, fill = "red", alpha = 0.1, colour = "black")

Created on 2019-09-02 by the reprex package (v0.3.0)

Copy link
Member

@thomasp85 thomasp85 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, but @hadley should chime in on breaking changes

@thomasp85 thomasp85 added this to the ggplot2 3.3.0 milestone Oct 1, 2019
@yutannihilation
Copy link
Member Author

Thanks! I'm wondering how this breaking change should be described on NEWS. I'll wait for Hadley's comment.

@hadley
Copy link
Member

hadley commented Oct 1, 2019

Can you explain how this would break existing code? I think the main change is that it won't draw vertical lines on either end, right?

@thomasp85
Copy link
Member

It's visually breaking, not something that makes any old code throw an error... I think we had a couple of those for the 3.2.0 release as well. But the NEWS will get rewritten come release so don't worry to much about it right now

@hadley
Copy link
Member

hadley commented Oct 1, 2019

Yeah, I wouldn't call this a breaking change then.

@thomasp85
Copy link
Member

It just occurred to me that this is related to and will influence request for only drawing upper line in geom_area(). As geom_area() subclasses geom_ribbon() it could/should be solved here as well. I suggest adding a parameter to GeomRibbon to control whether upper or both lines are drawn.

We should make sure that the correct line is drawn in geom_area if it dips or is fully below 0

@yutannihilation
Copy link
Member Author

Yeah, I wouldn't call this a breaking change then.

Thanks, I got it.

I suggest adding a parameter to GeomRibbon to control whether upper or both lines are drawn.

Thanks, I'll consider this. But, I didn't think about the impact on GeomArea carefully, let me check...

@yutannihilation
Copy link
Member Author

Hmm... after some thinking, now I'm not sure if this is OK for geom_area(). How do you feel?

devtools::load_all("~/Documents/repo/ggplot2")
#> Loading ggplot2

huron <- base::data.frame(year = 1875:1972, level = as.vector(LakeHuron))
h <- ggplot(huron, aes(year))
h + geom_area(aes(y = level), alpha = 0.5, colour = "black", size = 2)

Created on 2019-10-08 by the reprex package (v0.3.0)

We probably need these 3 types of Geoms here.

  • GeomRibbon with separate lines on the upper and the lower
  • GeomArea with a closed outline (as-is)
  • GeomArea with separate lines on the upper and the lower

@thomasp85
Copy link
Member

I think geom_area() should only have the upper line drawn. This is how most people envision it and is a common request. You can potentially make the behaviour a optional so the old full stroking is accessible.

I don't think anyone wants area plots with upper and lower

@yutannihilation
Copy link
Member Author

I don't think anyone wants area plots with upper and lower

After some thinking, now I agree with you here.

I think there are some options to implement this. For example:

  1. upper_colour and lower_colour, which overrides colour for the upper line and the lower line individually (c.f. Allow select alpha to be applied on fill (default), colour, or both #3485 (comment))
  2. Add an option to choose how the line should be drawn (e.g. "both", "upper", "lower", or "polygon").

I'll use the option 2 because it might be good to provide an option to fall back to the previous behavour (polygon).

@yutannihilation
Copy link
Member Author

We should make sure that the correct line is drawn in geom_area if it dips or is fully below 0

It seems I need to add some tweaks on geom_area(), though I'm yet to figure out why these differ.

devtools::load_all("~/Documents/repo/ggplot2/")
#> Loading ggplot2

d <- base::data.frame(x = 1:10, y = 5 - 1:10)

p1 <- ggplot(d, aes(x, ymin = 0, ymax = y)) +
  geom_ribbon(alpha = 0.3, colour = "red", size = 3, outline.type = "upper")

p2 <- ggplot(d, aes(x, y)) +
  geom_area(alpha = 0.3, colour = "red", size = 3, outline.type = "upper")

patchwork::wrap_plots(p1, p2)

Created on 2019-10-16 by the reprex package (v0.3.0)

@yutannihilation
Copy link
Member Author

Probably, I need another version of PositionStack to complete this? I can flip ymin and ymax when ymax is negative in GeomArea$draw_group(), but I feel it should be done on Position's side.

@thomasp85
Copy link
Member

This is not the domain of positions IMO... Pretty sure the reason for what you are seeing is that geom_area() has a pmin()/pmax() call on the y range somewhere before plotting. Maybe we can just remove that?

@yutannihilation
Copy link
Member Author

geom_area() has a pmin()/pmax() call on the y range somewhere before plotting

I too thought so at first, but GeomArea does nothing special at the drawing stage.

ggplot2/R/geom-ribbon.r

Lines 170 to 187 in 40e8b60

GeomArea <- ggproto("GeomArea", GeomRibbon,
default_aes = aes(colour = NA, fill = "grey20", size = 0.5, linetype = 1,
alpha = NA),
required_aes = c("x", "y"),
setup_params = function(data, params) {
params$flipped_aes <- has_flipped_aes(data, params, ambiguous = TRUE)
params
},
setup_data = function(data, params) {
data$flipped_aes <- params$flipped_aes
data <- flip_data(data, params$flipped_aes)
data <- transform(data[order(data$PANEL, data$group, data$x), ], ymin = 0, ymax = y)
flip_data(data, params$flipped_aes)
}
)

I think the call of pmax() and pmin() in question is done in pos_stack(), which is used in PositionStack$compute_panel().

ggplot2/R/position-stack.r

Lines 220 to 221 in 40e8b60

df$ymin <- pmin(heights[-n], heights[-1])
df$ymax <- pmax(heights[-n], heights[-1])

@thomasp85
Copy link
Member

damn, you are probably right... that is inconvenient

Maybe position_stack should keep track of which values it flip and switch them back in the end

@thomasp85
Copy link
Member

thomasp85 commented Dec 16, 2019

I've created a PR that addresses the observed problems with geom_area

I think we should have a switch for the new behaviour, both in geom_ribbon and geom_area... there will be some breaking visual changes and giving people a way to opt out will be good. I think the new behaviour should be default, though. Am I right in that the current implementation doesn't allow for old-style full stroking?

@thomasp85
Copy link
Member

@yutannihilation can you check that everything is as it should be after the merge of #3673? If so I think we can finish this off

yutannihilation and others added 2 commits December 18, 2019 12:09
Co-Authored-By: Thomas Lin Pedersen <thomasp85@gmail.com>
@yutannihilation
Copy link
Member Author

Thanks, confirmed the problem is now fixed after #3673.

devtools::load_all("~/Documents/repo/ggplot2/")
#> Loading ggplot2

d <- base::data.frame(x = 1:10, y = 5 - 1:10)

p1 <- ggplot(d, aes(x, ymin = 0, ymax = y)) +
  geom_ribbon(alpha = 0.3, colour = "red", size = 3, outline.type = "upper")

p2 <- ggplot(d, aes(x, y)) +
  geom_area(alpha = 0.3, colour = "red", size = 3, outline.type = "upper")

patchwork::wrap_plots(p1, p2)

Created on 2019-12-18 by the reprex package (v0.3.0)

I think we should have a switch for the new behaviour, both in geom_ribbon and geom_area... there will be some breaking visual changes and giving people a way to opt out will be good. I think the new behaviour should be default, though. Am I right in that the current implementation doesn't allow for old-style full stroking?

Sorry, I missed this comment. Yes, I agree with you and the users can specify "legacy" option for the old-style full stroking. I'll add a NEWS item to describe the change briefly. Do I need to add some examples for this?

ggplot2/R/geom-ribbon.r

Lines 25 to 27 in 7c6d3d5

#' @param outline.type Type of the outline of the area; `"both"` draws both the
#' upper and lower lines, `"upper"` draws the upper lines only. `"legacy"`
#' draws a closed polygon around the area.

@yutannihilation
Copy link
Member Author

I'm merging this. Thanks for reviewing multiple times!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

new geom suggestion: separate lines for upper and lower intervals
3 participants